Lektion 45



Eins der größten Ziele in 3D Applikationen ist Geschwindigkeit. Sie sollten die Anzahl der Polygone, die Sie tatsächlich rendern immer limitieren, etweder durch Sortierung, Culling oder Level-of-Detail Algorithmen. Wie dem auch sei, wenn alles andere fehl schlägt und Sie rohe Polygon-Pushing-Power benötigen, können Sie sich die Optimierungen zu nutze machen, die Ihnen von OpenGL zur Verfügung gestellt werden. Vertex Arrays sind ein guter Weg um das zu machen plus ein paar neue Extensionen der Graifkkartern, namens Vertex Buffer Objekte lässt den Traum einer jeden FPS Steigerung wahr werden. Die Extension ARB_vertex_buffer_object funktioniert wie ein Vertex Array, mit dem Unterschied, dass es die Daten in den Grafikkarten High-Performance-Speicher lädt, was die Rendering-Zeit drastisch reduziert. Da die Extension relativ neu ist, unterstüzen logischerweise nicht alle Karte diese, weshalb wir das überprüfen müssen.

In diesem Tutorial werden wir

Fangen wir also an! Als erstes definieren wir ein paar Applikations Parameter.

#define MESH_RESOLUTION 4.0f							// Pixel Pro Vertex
#define MESH_HEIGHTSCALE 1.0f							// Mesh Höhen Skalierung
//#define NO_VBOS								// wenn definiert, werden VBOs ausgeschaltet

Die ersten beiden Konstanten sind Standard Heightmap Werte - der erste setzt die Auflösung mit der die Heightmap pro Pixel generiert wird und der letztere setzt die vertikale Skalierung der Daten, die von der Heightmap ermittelt werden. Die dritte Konstante, wenn sie definiert wird, schaltet VBOs aus - nur eine Massnahme die ich für die Leute eingefügt habe, die eine High-End-GRafikkarte haben, damit diese sich den Unterschied ansehen können.

Als nächstes haben wir die VBO Extensions Konstanten, Datentypen und Funktionszeiger Definitionen.

// VBO Extension Definitionen, aus der glext.h
#define GL_ARRAY_BUFFER_ARB 0x8892
#define GL_STATIC_DRAW_ARB 0x88E4
typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, int size, const GLvoid *data, GLenum usage);

// VBO Extension Funktionszeiger
PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL;					// VBO Namens Generations-Prozedur
PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL;					// VBO Bind-Prozedur
PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL;					// VBO Daten-Lade-Prozedur
PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL;				// VBO Lösch-Prozedur

Ich habe nur das inkludiert, was für das Demo notwendig ist. Wenn Sie mehr Funktionalitäten benötigen, empfehle ich Ihnen, die neueste glext.h von http://www.opengl.org herunterzuladen und die Definitionen von dort zu verwenden (das macht Ihren Code ohnehin lesbarer). Wir gehen näher auf die Eigenarten der Funktionen ein, sobald wir diese verwenden.

Nun kommen die Standard-Mathematischen-Funktionen sowie unsere Mesh-Klasse. Alle sind recht rudimentär und speziell nur für dieses Demo designed. Wie immer, empfehle ich Ihnen, ihre eigene Mathe-Bibliothek zu entwickeln.

class CVert									// Vertex Klasse
{
public:
	float x;								// X Komponente
	float y;								// Y Komponente
	float z;								// Z Komponente
};
typedef CVert CVec;								// Die Definitionen sind synonym

class CTexCoord									// Texturkoordinaten Klasse
{
public:
	float u;								// U Komponente
	float v;								// V Komponente
};

class CMesh
{
public:
	// Mesh Daten
	int		m_nVertexCount;						// Vertex Anzahl
	CVert*		m_pVertices;						// Vertex Daten
	CTexCoord*	m_pTexCoords;						// Texturkoordinaten
	unsigned int	m_nTextureId;						// Textur ID

	// Vertex Buffer Objekt Namen
	unsigned int	m_nVBOVertices;						// Vertex VBO Name
	unsigned int	m_nVBOTexCoords;					// Texturkoordinaten VBO Name

	// Temporäre Daten
	AUX_RGBImageRec* m_pTextureImage;					// Heightmap Daten

public:
	CMesh();								// Mesh Konstruktor
	~CMesh();								// Mesh Destruktor

	// Heightmap Loader
	bool LoadHeightmap( char* szPath, float flHeightScale, float flResolution );
	// einzelne Punkt Höhe
	float PtHeight( int nX, int nY );
	// VBO Build Funktion
	void BuildVBOs();
};

Der meiste Code ist ziemlich selbsterklärend. Beachten Sie, dass ich die Vertex und Texturkoordinaten Daten seperat halte, obwohl das nicht unbedingt zwingend ist, wie ich später noch zeigen werde.

Hier haben wir unsere globale Variablen. Als erstes haben wir ein VBO Extension Validierungs Flag, welches im Initialisierungs Code gesetzt wird. Dann haben wir unser Mesh, gefolgt von unserem Y Rotations Zähler. Es folgen die FPS Überwachungs Variablen. Ich habe mich entschieden einen FPS Counter einzufügen, als Hilfe um die Optimierungen, die durch diesen Code erfolgen, anzuzeigen.

bool		g_fVBOSupported = false;					// wird ARB_vertex_buffer_object unterstützt?
CMesh*		g_pMesh = NULL;							// Mesh Daten
float		g_flYRot = 0.0f;						// Rotation
int		g_nFPS = 0, g_nFrames = 0;					// FPS und FPS Zähler
DWORD		g_dwLastFPS = 0;						// Letzte gemessene FPS Zeit

Lassen Sie zu den CMesh Funktions Definitionen springen, beginnend mit LoadHeightmap. Für die, die es noch nicht wissen, eine Heightmap ist ein zwei-dimensionales Datenfeld, in der Regel ein Image, welche die vertikalen Terrain Mesh-Daten spezifiziert. Es gibt viele Wege, eine Heightmap zu implementieren und sicherlich nicht nur einen richtige Art. Meine Implementation liest ein drei Channel-Bitmap ein und verwendet den Luminosity Algorithmus, um die Höhe der Daten zu bestimmen. Die resultierenden Daten würde exakt die selben sein, wenn das Bild farbig oder grauskaliert wäre, was es erlaubt, dass die Heightmap farbig ist. Ich persönlich empfehle ein 4 Channel-Image, wie zum Beispiel Targa (Anm. des Übersetzers: das sind .TGA Dateien) und würde den Alpha-Kanal für die Höhen verwenden. Wie dem auch sei, für dieses Tutorial habe ich mich für ein einfaches Bitmap entschieden.

Als erstes stellen wir sicher, dass die Heightmap existiert und wenn dem so ist, laden wir sie mit dem GLaux Bitmap Loade. Ja, ja, es wäre wahrscheinlich besser eine eigene Image-Lade-Routine zu schreiben, aber das ist nicht Ziel dieses Tutorials.

bool CMesh :: LoadHeightmap( char* szPath, float flHeightScale, float flResolution )
{
	// Fehler-Überprüfung
	FILE* fTest = fopen( szPath, "r" );					// Öffne das Image
	if( !fTest )								// stelle sicher, das es gefunden wurde
		return false;							// wenn nicht, fehlt die Datei
	fclose( fTest );							// Handle wird nicht mehr benötigt

	// Lade Textur Daten
	m_pTextureImage = auxDIBImageLoad( szPath );				// benutze GLaux's Bitmap Lade Routine

Nun werden die Dinge etwas interessanter. Als erstes möchte ich darauf hinweisen, dass meine Heightmap drei Vertices für jedes Dreick generiert wird - es gibt keine gemeinsamen Vertices. Ich werde später erklären, warum ich das gemacht habe, aber ich denke, dass Sie das wissen sollten, bevor Sie sich den Code anschauen.

Ich fange mit der Berechnung der Vertices-Menge in dem Mesh an. Der Algorithmus funktioniert grundsätzlich so: ( ( Terrain Breite / Auflösung ) * ( Terrain Länge / Auflösung ) * 3 Vertices in einem Dreieck * 2 Dreiecke in einem Quadrat ). Dann alloziiere ich meine Daten und fange an, das Vertex Feld durchzuarbeiten und die Daten zu setzen.

	// Generiere Vertex Feld
	m_nVertexCount = (int) ( m_pTextureImage->sizeX * m_pTextureImage->sizeY * 6 / ( flResolution * flResolution ) );
	m_pVertices = new CVec[m_nVertexCount];					// Alloziiere Vertex Daten
	m_pTexCoords = new CTexCoord[m_nVertexCount];				// Alloziiere Texturkoordinaten-Daten
	int nX, nZ, nTri, nIndex=0;						// erzeuge Variablen
	float flX, flZ;
	for( nZ = 0; nZ < m_pTextureImage->sizeY; nZ += (int) flResolution )
	{
		for( nX = 0; nX < m_pTextureImage->sizeX; nX += (int) flResolution )
		{
			for( nTri = 0; nTri < 6; nTri++ )
			{
				// mit diesem schnellen Hack finden wir die X,Z Position des Punktes heraus
				flX = (float) nX + ( ( nTri == 1 || nTri == 2 || nTri == 5 ) ? flResolution : 0.0f );
				flZ = (float) nZ + ( ( nTri == 2 || nTri == 4 || nTri == 5 ) ? flResolution : 0.0f );

				// Setze die Daten, benutze PtHeight um den Y-Wert zu erhalten
				m_pVertices[nIndex].x = flX - ( m_pTextureImage->sizeX / 2 );
				m_pVertices[nIndex].y = PtHeight( (int) flX, (int) flZ ) *  flHeightScale;
				m_pVertices[nIndex].z = flZ - ( m_pTextureImage->sizeY / 2 );

				// strecke die Textur über das gesamte Mesh
				m_pTexCoords[nIndex].u = flX / m_pTextureImage->sizeX;
				m_pTexCoords[nIndex].v = flZ / m_pTextureImage->sizeY;

				// Inkrementiere unseren Index
				nIndex++;
			}
		}
	}
 

Ich beende die Funktion mit dem Laden der Heightmap Textur und dem freigeben unsere Daten-Kopie. Das sollte Ihnen aus vorherigen Tutorials bekannt sein.

	// Lade die Textur
	glGenTextures( 1, &m_nTextureId );					// ermittle eine freie ID
	glBindTexture( GL_TEXTURE_2D, m_nTextureId );				// Binde die Textur
	glTexImage2D( GL_TEXTURE_2D, 0, 3, m_pTextureImage->sizeX, 
m_pTextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, 
m_pTextureImage->data );
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

	// gebe die Textur-Daten wieder frei
	if( m_pTextureImage )
	{
		if( m_pTextureImage->data )
			free( m_pTextureImage->data );
		free( m_pTextureImage );
	}
	return true;
}

PtHeight ist relativ simple. Es berechnet den Index der angeforderten Daten, wrappt jegliche Überläufe, um Fehler zu vermeinden und berechnet die Höhe. Die Luminanz Formel ist sehr einfach, wie Sie sehen können, als keine Angst.

float CMesh :: PtHeight( int nX, int nY )
{
	// Berechne die Position in der Textur, darauf achten, dass keine Überlauf stattfindet
	int nPos = ( ( nX % m_pTextureImage->sizeX )  + ( ( nY % m_pTextureImage->sizeY ) * m_pTextureImage->sizeX ) ) * 3;
	float flR = (float) m_pTextureImage->data[ nPos ];			// ermittle die rote Komponente
	float flG = (float) m_pTextureImage->data[ nPos + 1 ];			// ermittle die grüne Komponente
	float flB = (float) m_pTextureImage->data[ nPos + 2 ];			// ermittle die blaue Komponente
	return ( 0.299f * flR + 0.587f * flG + 0.114f * flB );			// berechne die Höhe mit dem Luminance Algorithmus
}

Hurra, Zeit um mit den Vertex Arrays und VBOs sich zu befassen. Was sind eigentlich Vertex Arrays? Grundsätzlich ist es ein System mit dem Sie OpenGL auf Ihre geometrischen Daten zeigen lassen können und dann danach Daten mit relativ wenigen Aufrufen rendern können. Die resultierenden Einsparungen bei den Funktionsaufrufen (glVertex, etc.) ermöglichen eine signifikante Geschwindigkeitssteigerung. Was sind VBOs? Nun, Vertex Buffer Objekte verwenden high-performance Grafikkartenspeicher anstatt Ihres Standard Ram-alloziierten Speichers. Das vermindert nicht nur die Speicheroperationen bei jedem Frame, sondern reduziert auch die Bus-Distanz, die Ihre Daten wandern muss. Bei meinen Spezifikationen verdreifachen VBOs sogar die Framerate, was sonst nicht so leicht zu erreichen ist.

So, nun erzeugen wir die Vertex Buffer Objekte. Es gibt tatsächlich ein paar Wege, um das zu machen, einer davon wird 'in den Speicher "mappen"' genannt. Ich denke, dass der einfachste Weg hier der beste ist. Der Prozess ist wie folgt: als erstes benutzen Sie glGenBufferARB um einen gültigen VBO "Namen" zu erhalten. Eigentlich ist ein Name eine ID Nummer, welche OpenGL mit Ihren Daten assoziiert. Wir wollen einen Namen generieren, da der selbe nicht immer verfügbar sein wird. Als nächstes machen wir das VBO zum gerade aktiven, indem wird es mit glBindBufferARB binden. Zu letzt laden wir die Daten in unsere Grafikkarten Daten, mit einem glBufferDataARB Aufruf, welchem wir die Größe und den Zeiger auf die Daten übergeben. glBufferDataARB kopiert die Daten in unseren Grafikkartenspeicher, was für uns bedeutet, dass wir diese nicht länger behalten müssen und somit löschen können.

void CMesh :: BuildVBOs()
{
	// Generiere und Binde den Vertex Buffer
	glGenBuffersARB( 1, &m_nVBOVertices );					// ermittle einen gültigen Namen
	glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOVertices );			// Binde den Buffer
	// Lade die Daten
	glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*3*sizeof(float), m_pVertices, GL_STATIC_DRAW_ARB );

	// Generiere und Binde den Textur Koordinaten Buffer
	glGenBuffersARB( 1, &m_nVBOTexCoords );					// ermittle einen gültigen Namen
	glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOTexCoords );		// Binde den Buffer
	// Lade die Daten
	glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*2*sizeof(float), m_pTexCoords, GL_STATIC_DRAW_ARB );

	// unsere Kopie der Daten wird nicht länger benötigt, diese sind sicher in der Grafikkarte
	delete [] m_pVertices; m_pVertices = NULL;
	delete [] m_pTexCoords; m_pTexCoords = NULL;
}

Ok, Zeit für die Initialisierung. Als erstes alloziieren und laden wir unsere Mesh Daten. Dann überprüfen wir, ob GL_ARB_vertex_buffer_object unterstütze wird. Wenn ja, ermittlen wir die Funktionszeiger mit wglGetProcAdress und erzeugen unsere VBOs. Beachten Sie, dass wenn VBOs nicht unterstützt wird, die Daten einfach beibehalten werden. Beachten Sie auch die Vorkehrung zur Erzwingung der Nicht-Verwendung von VBOs.

	// Lade die Mesh Daten
	g_pMesh = new CMesh();							// Instanziiere unser Mesh
	if( !g_pMesh->LoadHeightmap( "terrain.bmp",				// Lade unsere Heightmap
				MESH_HEIGHTSCALE, MESH_RESOLUTION ) )
	{
		MessageBox( NULL, "Error Loading Heightmap", "Error", MB_OK );
		return false;
	}

	// überprüfe ob VBOs unterstützt werden
#ifndef NO_VBOS
	g_fVBOSupported = IsExtensionSupported( "GL_ARB_vertex_buffer_object" );
	if( g_fVBOSupported )
	{
		// ermittle Zeiger auf die GL Funktionen
		glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
		glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
		glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
		glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
		// Lade Vertex Daten in den Grafikkartenspeicher
		g_pMesh->BuildVBOs();						// erzeuge die VBOs
	}
#else /* NO_VBOS */
	g_fVBOSupported = false;
#endif

IsExtensionSupported ist eine Funktion die Sie von OpenGL.org bekommen können. Meine Variante, ist meines Erachten snach, etwas sauberer.

bool IsExtensionSupported( char* szTargetExtension )
{
	const unsigned char *pszExtensions = NULL;
	const unsigned char *pszStart;
	unsigned char *pszWhere, *pszTerminator;

	// Extension Namen sollten keine Leerzeichen enthalten
	pszWhere = (unsigned char *) strchr( szTargetExtension, ' ' );
	if( pszWhere || *szTargetExtension == '\0' )
		return false;

	// ermittle Extensions String
	pszExtensions = glGetString( GL_EXTENSIONS );

	// such nach einer exakten Kopie des Extensions Strings
	pszStart = pszExtensions;
	for(;;)
	{
		pszWhere = (unsigned char *) strstr( (const char *) pszStart, szTargetExtension );
		if( !pszWhere )
			break;
		pszTerminator = pszWhere + strlen( szTargetExtension );
		if( pszWhere == pszStart || *( pszWhere - 1 ) == ' ' )
			if( *pszTerminator == ' ' || *pszTerminator == '\0' )
				return true;
		pszStart = pszTerminator;
	}
	return false;
}

Sie ist relativ simpel. Einige Leute benutzen einfach eine Sub-String Suche mit strstr, aber so wie es aussieht vertraut OpenGL.org nicht genug auf die Konsistenz von Extension Strings, um das als Beweis zu akzeptieren. Und hey, ich will mich nicht mit den Jungs streiten.

Fast fertig! Wie müssen nur noch die Daten rendern.

void Draw (void)
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// lösche Screen und Depth Buffer
	glLoadIdentity ();							// Resette die Modelview Matrix

	// ermittle FPS
	if( GetTickCount() - g_dwLastFPS >= 1000 )				// wenn eine Sekunden vergangen ist...
	{
		g_dwLastFPS = GetTickCount();					// aktualisiere unsere Zeit-Variable
		g_nFPS = g_nFrames;						// speichere die FPS
		g_nFrames = 0;							// Resette den FPS Zähler

		char szTitle[256]={0};						// erzeuge den Titel-String
		sprintf( szTitle, "Lesson 45: NeHe & Paul Frazee's VBO Tut - %d 
Triangles, %d FPS", g_pMesh->m_nVertexCount / 3, g_nFPS );
		if( g_fVBOSupported )						// füge eine Notiz über VBOs ein
			strcat( szTitle, ", Using VBOs" );
		else
			strcat( szTitle, ", Not Using VBOs" );
		SetWindowText( g_window->hWnd, szTitle );			// Setze den Titel
	}
	g_nFrames++;								// Inkrementiere unseren FPS Zähler

	// bewege die Kamera
	glTranslatef( 0.0f, -220.0f, 0.0f );					// bewege über das Terrain
	glRotatef( 10.0f, 1.0f, 0.0f, 0.0f );					// schaue etwas runter
	glRotatef( g_flYRot, 0.0f, 1.0f, 0.0f );				// Rotiere die Kamera

Ziemlich einfach - jede Sekunde wird der Frame-Zähler als FPS gespeichert und dann wird der Frame-Zähler resettet. Ich habe mich entschlossen die Polygonenanzahl mit einwirken zu lassen. Dann bewegen wir die Kamera über das Terrain (Sie müssen das vielleicht anpassen, wenn Sie die Heighmap ändern) und machen ein paar Rotationen. g_flYRot wird in der Update Funktion inkrementiert.

Um Vertex Arrays (und VBOs) zu benutzen, müssen Sie OpenGL mitteilen, was für Daten Sie im Speicher liegen haben. Der erste Schritt ist es also den Client Status GL_VERTEX_ARRAY und GL_TEXTURE_COORD_ARRAY zu aktivieren. Dann setzen wir unsere Zeiger. Ich bezweifel, dass Sie das bei jedem Frame machen müssen, es sei denn, Sie haben mehrere Meshes, aber da uns das Geschwindigkeitsmäßig nicht gerade weh tut, sehe ich hier kein Problem, das so zu machen.

Um einen Zeiger auf einen bestimmten Datentypen zu setzen, müssen Sie die richtige Funktion verwenden - glVertexPointer und glTexCoordPointer, in unserem Fall. Die Benutzung ist ziemlich einfach - übergeben Sie die Anzahl der Variablen in einem Punkt (drei für ein Vertex, zwei für ein texcoord), den Daten Cast (float), den Abstand zwischen den gewünschten Daten (für den Fall, dass die Vertices nicht alleine in der Struktur gespeichert werden) und den Zeiger auf die Daten. Sie können eigentlich glInterleavedArrays verwenden und all Ihre Daten in einem großen Buffer speichern, aber ich habe sie getrennt gehalten, um Ihnen zu zeigen, wie man meherere VBOs verwendet.

Wenn man von VBOs spricht so ist die Implementierung nicht viel anders. Die einzige wirkliche Änderung ist, anstatt einen Zeiger auf die Daten zu haben, binden wir das VBO, dass wir haben wollen und setzen den Zeiger auf Null. Schauen Sie selbst.

	// Setze Pointer auf unsere Daten
	if( g_fVBOSupported )
	{
		glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOVertices );
		glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );		// Setze den Vertex Pointer auf den Vertex Buffer
		glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOTexCoords );
		glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL );		// Setze den TexCoord Pointer auf den TexCoord Buffer
	} else
	{
		glVertexPointer( 3, GL_FLOAT, 0, g_pMesh->m_pVertices );	// Setze den Vertex Pointer auf unsere Vertex Daten
		glTexCoordPointer( 2, GL_FLOAT, 0, g_pMesh->m_pTexCoords );	// Setze den Vertex Pointer auf unsere TexCoord Daten
	}

Wissen Sie was? Rendern ist sogar noch einfacher.

	// Render
	glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount );		// zeichne alle Dreiecke auf einmal

Hier benutzen wir glDrawArrays, um unsere Daten an OpenGL zu senden. glDrawArrays überprüft welche Client Status aktiviert sind und benutzt dann deren Pointer zum rendern. Wir teilen die Geometrie Art, den Index mit dem wir starten wollen und wieviele Vertices gerendert werden sollen, mit. Es gibt noch viele andere Möglichkeiten wie wir unsere Daten zum rendern schicken können, wie zum Beispiel als glArrayElement, aber dies ist die schnellste Möglichkeit. Sie werden bemerken, dass glDrawArrays nicht innerhalb von glBegin / glEnd Statements ist. Das ist hier auch nicht notwendig.

glDrawArrays ist der Grund, warum ich meine Vertex-Daten nicht zwischen den Dreiecken teile - es ist nicht möglich. So weit ich weiß, ist die beste Möglichkeit den Speicherzugriff zu optimieren, die, dass man Triangle Strips verwendet, was wiederum nicht die Absicht des Tutorials war. Außerdem sollten Sie darauf achten, dass Normalenvektor-Operationen 'eine nach dem anderen' auf die Vertices angewandt werden, das heisst, wenn Sie Normalenvektoren verwenden, sollte jeder Vertex einen dazugehörigen Normalenvektor besitzen. Betrachten Sie das als Möglichkeit Ihre Normalenvektoren pro Vertex zu berechnen, was die visuelle Genauigkeit um einiges verbessern wird.

Alles was wir noch machen müssen, ist das Deaktiveren der Vertex Arrays und wir sind durch.

	// deaktivere Pointer
	glDisableClientState( GL_VERTEX_ARRAY );				// deaktviere Vertex Arrays
	glDisableClientState( GL_TEXTURE_COORD_ARRAY );				// deaktiviere Texturkoordinaten Arrays
}

Wenn Sie mehr Informationen über Vertex Buffer Objekte haben wollen, empfehle ich Ihnen die Dokumentation im SGI Extension Register zu lesen - http://oss.sgi.com/projects/ogl-sample/registry. Es ist etwas trockner zu lesen als ein Tutorial, aber bringt Ihnen wesentlich detailiertere Informationen.

Nun, das war's. Wenn Sie irgendwelche Fehler oder Fehlinformationen finden oder einfach Fragen haben, können Sie mich unter paulfrazee@cox.net. kontaktieren.